﻿using SemenQuest.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;

namespace SemenQuest
{

    public static class AgentController
    {
        private static volatile List<Agent> Agents;
        public static Boolean Running { get; private set; }
        #region Events & Handlers
        public class AgentEventArgs : EventArgs
        {
            public AgentEventArgs(AdditionalInfos additionalInfo)
            {
                AdditionalInfo = additionalInfo;
            }
            public AdditionalInfos AdditionalInfo { get; private set; }
            public enum AdditionalInfos
            {
                Unknown,
                Spawned,
                AgentControllerKill,
                LifeTimeEnded,
                WonTheLottery,
            }
        }
        public delegate void AgentHandler(Agent agent, AgentEventArgs e);
        public static event AgentHandler AgentStateChanged;
        #endregion

        #region Public Methods
        [MethodImpl(MethodImplOptions.Synchronized)]
        public static void Initialize()
        {
            if (!Running)
            {
                Agents = new List<Agent>();
                Running = true;
            }
        }
        [MethodImpl(MethodImplOptions.Synchronized)]
        public static Agent NewAgent(Agent.AgentTypes type)
        {
            if (!Running)
                throw new InvalidOperationException("Controller not initialized");

            Agent agent;
            if (type == Agent.AgentTypes.Semen)
                agent = new Semen();
            else if (type == Agent.AgentTypes.Egg)
                agent = new Egg();
            else
                throw new NotSupportedException("Unknown agent type");

            Agents.Add(agent);
            agent.AgentSpawned += AgentSpawned_Event;
            agent.AgentDied += AgentDied_Event;
            return agent;
        }
        [MethodImpl(MethodImplOptions.Synchronized)]
        public static void KillAgent(Agent agent)
        {
            if (!Running)
                throw new InvalidOperationException("Controller not initialized");
            if (agent == null)
                throw new NullReferenceException("Parameter 'agent' cannot be null");

            agent.KillAgent(true);
        }
        [MethodImpl(MethodImplOptions.Synchronized)]
        public static void KillAllAgents()
        {
            if (!Running)
                throw new InvalidOperationException("Controller not initialized");

            var copy = Agents.ToArray();
            foreach (Agent agent in copy)
            {
                agent.KillAgent(true);
            }
            Agents = new List<Agent>();
        }
        [MethodImpl(MethodImplOptions.Synchronized)]
        public static List<Agent> GetNearbyAgents(Agent agent)
        {
            if (!Running)
                throw new InvalidOperationException("Controller not initialized");
            if (agent == null)
                throw new NullReferenceException("Parameter 'agent' cannot be null");

            //Findes the distance between two agents (straight line)
            return (from a in Agents
                    where a.Id != agent.Id
                    where MathUtils.DistanceToTarget(agent.CenterPoint, a.CenterPoint) < agent.Visibility
                    select a).ToList();
        }
        [MethodImpl(MethodImplOptions.Synchronized)]
        public static Int32 GetAgentCount(Agent.AgentTypes type)
        {
            if (!Running)
                throw new InvalidOperationException("Controller not initialized");
            if (type == Agent.AgentTypes.None)
                throw new NullReferenceException("Parameter 'type' cannot be 'None'");

            return Agents.Count(x => x.AgentType == type);
        }
        [MethodImpl(MethodImplOptions.Synchronized)]
        public static List<Agent> GetAgentCount()
        {
            if (!Running)
                throw new InvalidOperationException("Controller not initialized");

            return Agents.ToList();
        }
        #endregion

        #region Private Methods
        [MethodImpl(MethodImplOptions.Synchronized)]
        private static void AgentSpawned_Event(Agent agent, AgentEventArgs e)
        {
            if (!Running)
                throw new InvalidOperationException("Controller not initialized");
            if (agent == null)
                throw new NullReferenceException("Parameter 'agent' cannot be null");

            AgentHandler handler = AgentStateChanged;
            if (handler != null)
                foreach (AgentHandler handle in handler.GetInvocationList())
                    handle.BeginInvoke(agent, e, EventEndInvoke, null);
        }
        [MethodImpl(MethodImplOptions.Synchronized)]
        private static void AgentDied_Event(Agent agent, AgentEventArgs e)
        {
            if (!Running)
                throw new InvalidOperationException("Controller not initialized");
            if (agent == null)
                throw new NullReferenceException("Parameter 'agent' cannot be null");

            Agents.Remove(agent);
            AgentHandler handler = AgentStateChanged;
            if (handler != null)
                foreach (AgentHandler handle in handler.GetInvocationList())
                    handle.BeginInvoke(agent, e, EventEndInvoke, null);
        }

        private static void EventEndInvoke(IAsyncResult iar)
        {
            var ar = (System.Runtime.Remoting.Messaging.AsyncResult)iar;
            var invokedMethod = (AgentHandler)ar.AsyncDelegate;
            try { invokedMethod.EndInvoke(iar); }
            catch { }
        }
        #endregion
    }
}
